/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "tansu_util.h"
#include "tansu_socket.h"

struct tansu_keyval *tansu_keyval_new(const char *key, const char *value)
{
    struct tansu_keyval *keyval;

    if ((keyval = calloc(1, sizeof(struct tansu_keyval))) == NULL)
        return NULL;

    if ((keyval->key = strdup(key)) == NULL)
        goto out;

    if ((keyval->value = strdup(value)) == NULL)
        goto out;

    return keyval;

out:
    free(keyval->key);
    free(keyval->value);
    free(keyval);
    return NULL;
}

void tansu_keyval_delete(struct tansu_keyval *keyval)
{
    if (keyval) {
        free(keyval->key);
        free(keyval->value);
        free(keyval);
    }
}

tansu_status_t
tansu_keyvals_append(struct tansu_keyvals *head,
                     const char *key,
                     const char *value)
{
    struct tansu_keyval *keyval;

    if ((keyval = tansu_keyval_new(key, value)) == NULL)
        return TANSU_MEMORY_ERROR;

    STAILQ_INSERT_TAIL(head, keyval, next);

    return TANSU_SUCCESS;
}

tansu_status_t
tansu_keyvals_add(struct tansu_keyvals *head,
                  const char *key,
                  const char *value)
{
    struct tansu_keyval *keyval;
    char *tmp;

    STAILQ_FOREACH(keyval, head, next) {
        if (strcasecmp(key, keyval->key) == 0) {
            if ((tmp = strdup(value)) == NULL)
                return TANSU_MEMORY_ERROR;
            free(keyval->value);
            keyval->value = tmp;
            return TANSU_SUCCESS;
        }
    }

    return tansu_keyvals_append(head, key, value);
}

tansu_status_t
tansu_keyvals_remove(struct tansu_keyvals *head, const char *key)
{
    struct tansu_keyval *keyval;

    STAILQ_FOREACH(keyval, head, next) {
        if (strcasecmp(key, keyval->key) == 0) {
            STAILQ_REMOVE(head, keyval, tansu_keyval, next);
            free(keyval->key);
            free(keyval->value);
            free(keyval);
            return TANSU_SUCCESS;
        }
    }

    return TANSU_NOT_FOUND;
}

struct tansu_keyval *
tansu_keyvals_get(const struct tansu_keyvals *head, const char *key)
{
    struct tansu_keyval *keyval;

    STAILQ_FOREACH(keyval, head, next) {
        if (strcasecmp(key, keyval->key) == 0)
            return keyval;
    }
    return NULL;
}

void
tansu_keyvals_clear(struct tansu_keyvals *head)
{
    struct tansu_keyval *keyval;
    STAILQ_FREE_ALL(keyval, head, next, tansu_keyval_delete);
}

tansu_status_t
tansu_keyvals_copy(struct tansu_keyvals *dest, const struct tansu_keyvals *src)
{
    struct tansu_keyval *keyval;
    struct tansu_keyvals head;
    tansu_status_t status;
    int empty = 0;

    STAILQ_INIT(&head);
    STAILQ_FOREACH(keyval, src, next) {
        status = tansu_keyvals_add(&head, keyval->key, keyval->value);
        if (status != TANSU_SUCCESS)
            goto out;
    }

    empty = STAILQ_EMPTY(dest);
    STAILQ_CONCAT(dest, &head);

    if (!empty) {
        // TODO: 
    }

    return TANSU_SUCCESS;

out:
    STAILQ_FREE_ALL(keyval, &head, next, tansu_keyval_delete);
    return status;
}

char *tansu_strndup(const char* s, uint32_t n)
{
    const char* p = s;
    char* copy;
    uint32_t len;

    while (*p && n > 0) {
        n--;
        p++;
    }

    len = p - s;

    copy = malloc(len + 1);
    if (copy) {
        memcpy(copy, s, len);
        copy[len] = '\0';
    }

    return copy;
}

char *
tansu_buffer_readline(char *buff, char **next)
{
    unsigned int i;
    size_t len;
    char b0 = 0;

    if (buff == NULL)
        return NULL;
   
    len = strlen(buff);

    for (i = 0; i < len; i++) {
        if (buff[i] == '\r' || buff[i] == '\n') {
            b0 = buff[i];
            break;
        }
    }

    if (i == len)
        return NULL;

    buff[i] = '\0';
	if (i < len - 1) {
        char b1 = buff[i + 1];
        if ((b1 == '\r' || b1 == '\n') && b0 != b1)
            i++;
    }

    if (next)
        *next = &buff[i + 1];

    return buff;
}

const char *tansu_skip_space(const char *s)
{
    while (*s == ' ' || *s == '\t') {
        s++;
    }
    return s;
}

int tansu_time_get_monotonic(struct timeval *tv)
{
#ifdef HAVE_CLOCK_GETTIME
    struct timespec ts;
#endif /* HAVE_CLOCK_GETTIME */

    if (tv == NULL)
        return -1;

#ifdef HAVE_CLOCK_GETTIME
    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
        tv->tv_sec = ts.tv_sec;
        tv->tv_usec = ts.tv_nsec / 1000;
        return 0;
    }
#else /* HAVE_CLOCK_GETTIME */
    return gettimeofday(tv, NULL);
#endif /* HAVE_CLOCK_GETTIME */

    return -1;
}

int tansu_time_get_timeout(struct timeval *tv, const struct timeval *target)
{
    int ret;
    struct timeval current;

    if (tv == NULL || target == NULL)
        return -1;

    ret = tansu_time_get_monotonic(&current);
    if (ret != 0)
        return ret;

    if (tansu_time_cmp(&current, target) >= 0) {
        tv->tv_sec = 0;
        tv->tv_usec = 0;
    } else {
        tv->tv_sec = target->tv_sec;
        tv->tv_usec = target->tv_usec;
        tansu_time_sub(tv, &current);
    }

    return 0;
}

int tansu_time_cmp(const struct timeval *tv1, const struct timeval *tv2)
{
    if (tv1->tv_sec == tv2->tv_sec) {
        return tv1->tv_usec - tv2->tv_usec;
    } else {
        return tv1->tv_sec - tv2->tv_sec;
    }
}

int tansu_get_gmt_iso1123(char *buff, int size)
{
    struct tm cur;
    time_t t;

    t = time(NULL);
    gmtime_r(&t, &cur);
    if (strftime(buff, size, "%a, %d %b %Y %H:%M:%S GMT", &cur) == 0)
        return -1;

    return 0;
}

uint16_t tansu_url_getport(const char *url)
{
    const char *tmp;
    unsigned long port;

    if (url == NULL)
        return 0;

    if ((tmp = strstr(url, "://")) == NULL)
        return 0;

    if ((tmp = strchr(&tmp[3], ':')) == NULL)
        return 0;

    port = strtoul(&tmp[1], NULL, 10);
    if (port == 0 || port > 65535)
        return 0;

    return port;
}

const char *tansu_url_getpath(const char *url)
{
    const char *tmp;

    if (url == NULL)
        return NULL;

    if ((tmp = strstr(url, "://")) == NULL)
        return NULL;

    return strchr(&tmp[3], '/');
}

int tansu_url_isaddr(const char *url, const struct sockaddr_storage *ss)
{
    const char *tmp;
    void *src;
    char buff[TANSU_IPv6_ADDR_LEN_MAX + 1];

    if (url == NULL || ss == NULL)
        return 0;

    if ((tmp = strstr(url, "://")) == NULL)
        return 0;

    if (ss->ss_family == AF_INET) {
        src = &((struct sockaddr_in *)ss)->sin_addr;
    } else if (ss->ss_family == AF_INET6) {
        src = &((struct sockaddr_in6 *)ss)->sin6_addr;
    } else {
        return 0;
    }

    memset(buff, 0, sizeof(buff));
    if (inet_ntop(ss->ss_family, src, buff, sizeof(buff)) == NULL)
        return 0;

    /* TODO: IPv6 support */
    return strncmp(buff, &tmp[3], strlen(buff)) == 0 ? 1 : 0;
}

tansu_status_t
tansu_url_parse(const char *url,
                char **scheme,
                char **user,
                char **pass,
                char **address,
                uint16_t *port,
                const char **path)
{
    //const char *head;
    const char *tmp;
    unsigned long n;
    //const char *p;

    if (url == NULL)
        return TANSU_INVALID_ARGUMENT;

    if (scheme)
        *scheme = NULL;
    if (user)
        *user = NULL;
    if (pass)
        *pass = NULL;
    if (address)
        *address = NULL;
    if (port)
        *port = 0;
    if (path)
        *path = NULL;

    if ((tmp = strstr(url, "://")) == NULL)
        return TANSU_INVALID_FORMAT;

    if (scheme)
       if ((*scheme = tansu_strndup(url, tmp - url)) == NULL)
           return TANSU_MEMORY_ERROR;

#if 0
    /* IPv6 */
    if (tmp[3] == '[') {
        char *end = strchr(&tmp[4], ']');
        if (end == NULL)
            return TANSU_INVALID_FORMAT;
        if (end[1] == ':') {
            if ((n = strtoul(&end[1], &end, 10)) > 65535)
                return TANSU_INVALID_FORMAT;
            if (port)
                *port = n;
        }
        if (address)
            if ((*address = tansu_strndup(&tmp[4], end - &tmp[4])) == NULL)
                return TANSU_MEMORY_ERROR;
        end = &end[1];
        if (path)
            *path = (end && end[0] != '\0') ? end : "/";
        return TANSU_SUCCESS;
    }
#endif

#if 1
    const char *addr_end = NULL;
    const char *path_end = NULL;
    if ((addr_end = strchr(&tmp[3], ':'))) {
        n = strtoul(&addr_end[1], (char **)&path_end, 10);
        if (n == 0 || n > 65535) {
            free(*scheme);
            *scheme = NULL;
            return TANSU_INVALID_FORMAT;
        }
        if (port)
            *port = n;
    } else {
        if ((addr_end = strchr(&tmp[3], '/')) == NULL)
            addr_end = &tmp[strlen(tmp)];
        path_end = addr_end;
    }
    if (address)
        if ((*address = tansu_strndup(&tmp[3], addr_end - &tmp[3])) == NULL) {
            free(*scheme);
            *scheme = NULL;
            return TANSU_MEMORY_ERROR;
        }
    if (path)
        *path = (path_end && path_end[0] != '\0') ? path_end : NULL;
    return TANSU_SUCCESS;
#endif

#if 0
    for (head = tmp = &tmp[3]; ; tmp = &tmp[1]) {
        switch (tmp[0]) {
        case '\0':
            if (address)
                if ((*address = strdup(head)) == NULL)
                    return TANSU_MEMORY_ERROR;
            if (port)
                *port = 0; /* TODO */
            return TANSU_SUCCESS;
        case ':':
            /* pass */
        case '@':
            p = head;
            break;
        case '/':
            if (address)
                if ((*address = tansu_strndup(head, tmp - head)) == NULL)
                    return TANSU_MEMORY_ERROR;
            if (path)
                *path = &tmp[0];
            return TANSU_SUCCESS;
        case '?':
            /* pass */
        case '#':
            return TANSU_INVALID_FORMAT;
        default:
            continue;
        }
        break;
    }

    head = &tmp[0]
#endif

    return TANSU_SUCCESS;
}

